/*******************************************************************************
 * Copyright (c) 2010, 2010 Xbeam Project and ChenZhiKong.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 *******************************************************************************/
package org.bellusoft.xbeam.framework;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.bellusoft.xbeam.contract.concurrent.GuardedBy;
import org.bellusoft.xbeam.contract.doc.OsgiSpec;
import org.bellusoft.xbeam.contract.doc.P;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.framework.launch.Framework;
import org.osgi.service.startlevel.StartLevel;

/**
 *
 */
public class FrameworkImpl implements Framework {
	
	private final String SYSTEM_BUNDLE_SYMBOLIC_NAME = "org.bellus.xbeam" ; 
	
	private final FrameworkConfig config ; 
	
	@GuardedBy("mutex")
	@OsgiSpec(chs=@P(ch="4.2.3",sl=1,el=1))
	private int state = Bundle.INSTALLED; 
	
	@GuardedBy("mutex")
	private BundleContextImpl context ; 
	
	private final ReentrantLock mutex = new ReentrantLock();
	
	private KernelImpl kernel ; 
	
	private Module module ; 
	
	private BundleStorage bundleStorage ; 
	
	private StartLevel startLevel ; 
	
	@GuardedBy("mutex")
	private final List<StopMessageEnvelope> stopMessageEnvelopes = new ArrayList<StopMessageEnvelope>(1);
	
	public FrameworkImpl(FrameworkConfig config) {
		this.config = config ; 
	}

	public Enumeration<URL> findEntries(String path, String filePattern,
			boolean recurse) {
		return null;
	}

	public long getBundleId() {
		return 0;
	}

	public URL getEntry(String path) {
		return null;
	}

	public Enumeration<String> getEntryPaths(String path) {
		return null;
	}

	@OsgiSpec(chs=@P(ch="6.2.2.2"))
	public String getLocation() {
		return Constants.SYSTEM_BUNDLE_LOCATION;
	}

	public String getSymbolicName() {
		//TODO: Alias Constants.SYSTEM_BUNDLE_SYMBOLICNAME must supported
		return SYSTEM_BUNDLE_SYMBOLIC_NAME;
	}

	@OsgiSpec(topic="lifecycle/framework", t="Initializing the Framework",chs={@P(ch="4.2.4"),@P(ch="6.2.2.4")})
	public void init() throws BundleException {
		
		mutex.lock() ; 
		try{
			//@OsgiSpec(6.2.2.4)
			if((state&(Bundle.STARTING|Bundle.ACTIVE)|Bundle.STOPPING)!=0){
				return ; 
			}
			kernel = new KernelImpl() ; 
			kernel.setCacheRoot(new File(config.getCacheDir()));
			kernel.init();
			kernel.enableEventDispatcher();
			context = new BundleContextImpl() ; 
			context.setKernel(kernel);
			Bundle[] persistedBundles = kernel.loadPersistBundles();
			installInitialBundles(persistedBundles);
			kernel.startStartLevelService() ; 
			state = Bundle.STARTING ; 
		}finally{
			mutex.unlock() ;
		}
	}


	private void installInitialBundles(Bundle[] persistedBundles) {
		Set<String> installedLocations = new HashSet<String>(persistedBundles.length);
		for(Bundle b: persistedBundles){
			installedLocations.add(b.getLocation());
		}
		List<InitialBundle> initialBundles = config.getInitialBundles() ; 
		List<InitialBundle> shouldInitBundles = new ArrayList<InitialBundle>();
		
		//compute initial bundles which need to install,
		//TODO: if persist storage already installed the bundle in the initial bundle list, we just ignore it, 
		//which may be controlled later by updateCache and overPersist field in the InitialBundle Class.
		for(InitialBundle initialBundle : initialBundles){
			if(!installedLocations.contains(initialBundle.getBundleUrl())){
				shouldInitBundles.add(initialBundle);
			}
		}
		
		for(InitialBundle initialBundle: shouldInitBundles){
			try{
				Bundle b = kernel.installBundle(initialBundle.getBundleUrl(), null, initialBundle.getStartSetting());
				startLevel.setBundleStartLevel(b, initialBundle.getStartLevel());
			}catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

	@OsgiSpec(topic="lifecycle/framework", t="Starting the Framework",chs={@P(ch="4.2.5"),@P(ch="6.2.2.5")})
	public void start() throws BundleException {
		mutex.lock() ; 
		try{
			unsafeStart();
		}finally{
			mutex.unlock() ;
		}
	}

	private void unsafeStart() throws BundleException {
		if((state&(Bundle.INSTALLED|Bundle.RESOLVED))!=0){
			init() ; 
		}
		if((state&Bundle.ACTIVE)!=0){
			return ; 
		}
		if((state&Bundle.STARTING)!=0){
			startBundles();
			state = Bundle.ACTIVE ; 
			publishFrameworkEvent(FrameworkEvent.STARTED);
		}else if((state&Bundle.STOPPING)!=0){
			//TODO:make surce stopping status should never be accessed by the method.
		}
	}

	private void publishFrameworkEvent(int frameworkEvent) {
		kernel.sendFrameworkEvent(frameworkEvent); 
	}

	private void startBundles() {
		//resolve all bundles 
		HostBundle[] bundles = kernel.getHostBundles();
		for(HostBundle hb : bundles){
			if(hb.getBundleId()!=0){
				hb.startAccordingPersistStartSetting();
			}
		}
		startLevel.setStartLevel(config.getFrameworkStartLevel());
		// TODO Any exceptions that occur 
//		during bundle starting must be wrapped in a BundleException and then 
//		published as a framework event of type FrameworkEvent.ERROR
	}

	public void start(int options) throws BundleException {
		start(); 
	}
	
	private class StopProcess implements Runnable{

		public void run() {
			mutex.lock() ; 
			try{
				unsafeStop(); 
				notifyWaitForStopThreads(FrameworkEvent.STOPPED);
			}finally{
				mutex.unlock();
			}
		}

	}
	
	private void notifyWaitForStopThreads(int frameworkEventType) {
		for(StopMessageEnvelope sm : stopMessageEnvelopes){
			sm.event = new FrameworkEvent(frameworkEventType,FrameworkImpl.this,null);
			sm.stopCondition.notify();
		}
		stopMessageEnvelopes.clear();
	}

	@OsgiSpec(topic="lifecycle/framework", t="Starting the Framework",chs={@P(ch="4.2.6"),@P(ch="6.2.2.7")})
	public void stop() throws BundleException {
		
		/*@OsgiSpec: The method returns immediately to the caller after initiating the following 
		 * steps to be taken on another thread. 
		 * */
		new Thread(new StopProcess()).start();
	}


	private void stopAllInstalledBundles() {
		Bundle[] bundles = kernel.getBundles() ;
		for(Bundle b : bundles){
			try {
				b.stop(Bundle.STOP_TRANSIENT);
			} catch (BundleException e) {
				publishFrameworkEvent(FrameworkEvent.ERROR, e);
			}
		}
	}

	public void stop(int options) throws BundleException {
		stop();
	}

	public void uninstall() throws BundleException {
		throw new BundleException("Framework cannot be uninstalled.");
	}
	
	private class UpdateProcess implements Runnable{

		public void run(){
			mutex.lock() ; 
			try{
				unsafeStop(); 
				notifyWaitForStopThreads(FrameworkEvent.STOPPED_UPDATE);
				try {
					unsafeStart();
				} catch (BundleException e) {
					//TODO: publish framework start error event 
					publishFrameworkEvent(FrameworkEvent.ERROR,e);
				}
			}finally{
				mutex.unlock();
			}
		}
		
	}

	public void update() throws BundleException {
		new Thread(new UpdateProcess()).start();
//		TODO: Throws BundleException –  If stopping and restarting this Framework could not be initialized
	}

	private void publishFrameworkEvent(int frameworkEvent, BundleException e) {
		kernel.sendFrameworkEvent(frameworkEvent,e);
	}

	public void update(InputStream in) throws BundleException {
		try{
			in.close() ; 
		}catch (Exception e) {
			//@ErrorIgnore 
		}
		update() ; 
	}
	
	private boolean shouldWaitForStop(){
		return (state==Bundle.ACTIVE)||(state==Bundle.STARTING)||(state==Bundle.STOPPING);
	}

	@OsgiSpec(topic="lifecycle/framework", t="Waiting for stop of the framework",
			chs={@P(ch="4.2.6"),@P(ch="6.2.2.12")},
			xbeamSpecific="waitForStop")
	public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
		/**
		 * @xbeamSpecific:start
		 * WaitFroStop method will create a message envelope and a wait condition set into the message envelope.
		 * the message envelope is then add to the global message envelope list。waitFroStop then wait for the 
		 * condition created. Stop method will check the global message envelope list，put stop event in the envelope
		 *  and notify all the thread waiting for the conditions in the envelope. When waitFroStop is waken up, it 
		 *  get and return stop event in the envelope created early.      
		 *  @xbeamSpecific:end
		 */
		if(timeout<0){
			throw new IllegalArgumentException("the value of timeout is negative");
		}
		mutex.lock() ; 
		try{
			if(!shouldWaitForStop()){
				return null ; 
			}
			Condition stopCondition = mutex.newCondition() ; 
			StopMessageEnvelope sme = new StopMessageEnvelope();
			sme.stopCondition = stopCondition ; 
			stopMessageEnvelopes.add(sme);
			while(shouldWaitForStop()){ //TODO: do we still need check ? 
				if(timeout==0){
					stopCondition.await();
				}else{
					boolean isTimeout = stopCondition.await(timeout, TimeUnit.MILLISECONDS);
					if(isTimeout){
						return new FrameworkEvent(FrameworkEvent.WAIT_TIMEDOUT,this,null);
					}
				}
			}
			return sme.event ; 
		}finally{
			mutex.unlock() ;
		}
	}
	
	private class StopMessageEnvelope{
		Condition stopCondition ; 
		FrameworkEvent event ; 
	}

	public <A> A adapt(Class<A> type) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException(); 
	}

	//TODO: if init() is not invoked, return null ; 
	public BundleContext getBundleContext() {
		mutex.lock() ; 
		try{
			return context ; 
		}finally{
			mutex.unlock() ;
		}
	}

	public File getDataFile(String filename) {
		return bundleStorage.getDataFile(filename);
	}

	public Dictionary<String, String> getHeaders() {
		return module.getHeaders();
	}

	public Dictionary<String, String> getHeaders(String locale) {
		return module.getHeaders(locale);
	}

	public long getLastModified() {
		return bundleStorage.getLastModifyTime();
	}

	public ServiceReference<?>[] getRegisteredServices() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException() ; 
	}

	public URL getResource(String name) {
		return module.getResource(name);
	}

	public Enumeration<URL> getResources(String name) throws IOException {
		return module.getResources(name);
	}

	public ServiceReference<?>[] getServicesInUse() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}

	public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(
			int signersType) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}

	public int getState() {
		mutex.lock() ; 
		try{
			return state ; 
		}finally{
			mutex.unlock() ;
		}
	}

	public Version getVersion() {
		return module.getVersion() ; 
	}

	public boolean hasPermission(Object permission) {
		throw new UnsupportedOperationException(); 
	}

	public Class<?> loadClass(String name) throws ClassNotFoundException {
		return module.loadClass(name);
	}

	public int compareTo(Bundle o) {
		return (int)(0-o.getBundleId());
	}

	private void unsafeStop() {
		//TODO: should stopping status return ? ref Figure 4.27
		if(state==Bundle.STOPPING||state==Bundle.INSTALLED||state==Bundle.RESOLVED){
			return ; 
		}
		state = Bundle.STOPPING ; 
		startLevel.setStartLevel(0);
		stopAllInstalledBundles();
		kernel.unregisterAllServices();
		context = null ; 
		kernel.releaseAllResources();
		state = Bundle.RESOLVED; 
	}

}
